﻿' Imports System.IO.Ports
' https://stackoverflow.com/questions/53904613/serialport-open-the-parameter-is-incorrect
' 可能是因为Cube里面USB驱动的问题，用系统自带的库打开STM32的虚拟串口会报
' “SerialPort.Open() --IOException — “The parameter is incorrect.”
' This exception often occurs With virtual (e.g. USB) COM ports that
' Do Not have an underlying physical RS232 implementation.
' Such ports Do Not manage state bits And because Of that SerialPort.
' Open() method raises IOException With Error 87 "The parameter is incorrect"
' When it tries To Set communication parameters For the serial port.
' System.IO.Ports.SerialPort class does Not support this case,
' but there are other implementations that you can use.
' For example, with SerialPortStream library (also available in NuGet)
' you can open serial COM port without setting communication parameters
' using SerialPortStream.OpenDirect() method

Imports RJCP.IO.Ports
Imports System.Threading
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports USBInterface
Imports System.Text

Public Class Transport

#Region "field"
    Public port As SerialPortStream
    Private _ready As Boolean
#End Region

    <Description("设备是否准备好")>
    Public ReadOnly Property Ready As Boolean
        Get
            Return _ready
        End Get
    End Property

    Public Sub New()
        _ready = False
    End Sub

    ''' <summary>
    ''' 列出可用的串口
    ''' </summary>
    ''' <returns></returns>
    Public Shared Function ListPorts() As ArrayList
        Dim ret As New ArrayList
        For Each p As String In SerialPortStream.GetPortNames()
            ret.Add(p)
        Next
        Return ret
    End Function

    ''' <summary>
    ''' 启动串口
    ''' </summary>
    ''' <param name="name">串口名称</param>
    ''' <param name="baudrate">波特率</param>
    ''' <returns>是否打开成功</returns>
    Public Function Begin(name As String, baudrate As Integer) As Boolean
        Dim ret As Boolean = False
        Try
            If port Is Nothing Then
                port = New SerialPortStream(name) With {
                .BaudRate = baudrate,
                .DataBits = 8,
                .Parity = Parity.None,
                .StopBits = StopBits.One
            }
            Else
                port.BaudRate = baudrate
                port.PortName = name
            End If
            port.ReadTimeout = 200
            port.OpenDirect()
            ret = True
            _ready = True
            Console.WriteLine($"{port.PortName} Open Success.")
        Catch ex As Exception
            Console.WriteLine($"[Error] {ex.Message}")
        End Try
        Return ret
    End Function


    ''' <summary>
    ''' 清空全部缓存
    ''' </summary>
    Public Sub Flush()
        port.DiscardInBuffer()
    End Sub

    Public Sub Close()
        If Ready Then
            port.Close()
            _ready = False
        End If
    End Sub

    ''' <summary>
    ''' 结构体转字节流
    ''' </summary>
    ''' <param name="valStruct">需要被转换的结构体</param>
    ''' <returns>转换结果</returns>
    ''' <remarks></remarks>
    Public Shared Function StructToByte(valStruct As Object) As Byte()
        Return StructToByte(valStruct, Marshal.SizeOf(valStruct))
    End Function

    Public Shared Function StructToByte(valStruct As Object, len As Integer) As Byte()
        Dim _size As Integer = len
        Dim _buffer As IntPtr = Marshal.AllocHGlobal(_size)
        Try
            Marshal.StructureToPtr(valStruct, _buffer, False)
            Dim _bytes(_size - 1) As Byte
            Marshal.Copy(_buffer, _bytes, 0, _size)
            Return _bytes
        Finally
            Marshal.FreeHGlobal(_buffer)
        End Try
    End Function


    ''' <summary>
    ''' 字节流转结构体
    ''' </summary>
    ''' <param name="_bytes">被转换的字节数组</param>
    ''' <param name="_structType">用.GetType()得到的类型</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function ByteToStruct(_bytes As Byte(), _structType As Type) As Object
        Dim _size As Integer = Marshal.SizeOf(_structType)
        Dim _buffer As IntPtr = Marshal.AllocHGlobal(_size)
        Try
            Marshal.Copy(_bytes, 0, _buffer, _size)
            Return Marshal.PtrToStructure(_buffer, _structType)
        Finally
            Marshal.FreeHGlobal(_buffer)
        End Try
    End Function

End Class

Public Class Calculate
    Shared Function Sum(ByVal buf() As Byte) As Byte
        Dim ret As Byte = 0
        Dim _sum As Integer = 0
        For Each b As Byte In buf
            _sum = (_sum + b) Mod 256
        Next
        ret = _sum
        Return ret
    End Function
End Class

Public Class WevMaster

    Public Const CURVE_PID_VEL As Byte = 1

    <Description("上位机参数")>
    Public Property WevHeartBeatMaster As WevHeartBeatMaster_T = New WevHeartBeatMaster_T()

    Public WevPidCurve As WevUserDefinedCurve_T = New WevUserDefinedCurve_T(CURVE_PID_VEL)

End Class

Public Class WevSlave

    <Description("驱动器参数")>
    Public Property WevDriver As WevHeartBeatSlave_T = New WevHeartBeatSlave_T()

    <Description("驱动器报警信息")>
    Public ReadOnly Property ErrorInfo As MotorTypeDef.Error
        Get
            Return WevDriver.DriverError
        End Get
    End Property

    Public _FilteredVel As Single

    <Description("滤波后的转速")>
    Public ReadOnly Property FilteredVel As Single
        Get
            Return _FilteredVel
        End Get
    End Property

    Public WevReply As WEVReply_T = New WEVReply_T()

End Class

Public Class SerialDriver : Inherits Transport

    Private Const MsgCurve As Byte = 2
    Private Const MsgHeartBeat As Byte = 3
    Private Const MinFrameLen As Integer = 6
    Private Const WevHead As Byte = &H5A
    Private Running As Boolean = True
    Private ReadThread As Thread

    <Description("上位机参数")>
    Public Property Master As WevMaster = New WevMaster()

    <Description("驱动器参数")>
    Public Property Slave As WevSlave = New WevSlave()

    <Description("是否需要发送Pid曲线")>
    Public Property DownloadingPidCurve As Boolean = False
    <Description("曲线技术是否一致")>
    Public ReadOnly Property DownloadCurveConfirmed As Boolean
        Get
            Return Master.WevPidCurve.count = Slave.WevReply.count
        End Get
    End Property

    Public Sub Dispose()
        Running = False
    End Sub

    Public Sub HeartBeat()
        Dim buf = GenerateMsg(MsgHeartBeat, Master.WevHeartBeatMaster)
        SendMsg(buf)
    End Sub

    Public Sub DownloadPicCurveAnsyc()
        DownloadingPidCurve = True
    End Sub

    Public Sub DownloadPidCurve()
        If Master.WevPidCurve.count = 255 Then
            Master.WevPidCurve.count = 1
        Else
            Master.WevPidCurve.count += 1
        End If
        Dim buf = GenerateMsg(MsgCurve, Master.WevPidCurve)
        SendMsg(buf)
        DownloadingPidCurve = False
    End Sub

    Public Sub SendMsg(msg As String)
        Dim buf = Encoding.ASCII.GetBytes(msg)
        SendMsg(buf)
    End Sub

    Private Sub SendMsg(buf As Byte())
        If port Is Nothing Then
            Console.WriteLine("port not available.")
            Return
        End If

        If Not Ready Then
            Return
        End If

        Try
            port.Write(buf, 0, buf.Length)
        Catch ex As Exception
            Console.WriteLine($"[Error] {ex.Message}")
            Close()
        End Try
    End Sub

    ''' <summary>
    ''' 生成要发送的协议
    ''' </summary>
    ''' <param name="msg_id">信息类型</param>
    ''' <param name="data_struct">数据结构体</param>
    ''' <returns></returns>
    Public Function GenerateMsg(msg_id As Byte, data_struct As Object) As Byte()
        Dim tx_buffer(4) As Byte
        Dim data_buf() As Byte = Nothing
        Dim data_len As Integer = Marshal.SizeOf(data_struct)
        tx_buffer(0) = &H5A
        tx_buffer(1) = msg_id
        tx_buffer(2) = Convert.ToByte(data_len And &HFF)
        tx_buffer(3) = Convert.ToByte((data_len And &HFF00) >> 8)
        ReDim Preserve tx_buffer(data_len + MinFrameLen - 1) 'head(1), msg_id(1), len(2), crc32(1), tail(1), vb.net是以最后索引的下标定义数组的
        data_buf = StructToByte(data_struct)
        If data_buf IsNot Nothing Then
            Array.Copy(data_buf, 0, tx_buffer, 4, data_buf.Length)
        End If
        '计算校验和CRC
        Dim crc As Byte = Calculate.Sum(data_buf)
        tx_buffer(tx_buffer.Length - 2) = crc
        tx_buffer(tx_buffer.Length - 1) = &H7E
        Return tx_buffer

    End Function

    Public Sub StartRead()
        If ReadThread IsNot Nothing Then
            Return
        End If
        ReadThread = New Thread(Sub()
                                    Dim last_bytes = 0
                                    While True
                                        If port.BytesToRead = last_bytes And last_bytes <> 0 Then
                                            Dim buf(last_bytes - 1) As Byte
                                            port.Read(buf, 0, last_bytes)
                                            Parse(buf)
                                            port.DiscardInBuffer()
                                        End If
                                        last_bytes = port.BytesToRead
                                        Thread.Sleep(10)
                                        If Not Running Then
                                            Return
                                        End If
                                    End While
                                End Sub)
        ReadThread.Start()
    End Sub

    Public Function Parse(buf As Byte()) As Boolean
        Dim ret = True
        Dim data_len As Integer = 1
        Dim data_buf(0) As Byte

        If buf(0) <> WevHead Then
            Return False
        End If
        If buf.Length < MinFrameLen Then
            Return False
        End If
        data_len = buf(2) + (CUInt(buf(3)) << 8)
        If buf.Length < data_len + MinFrameLen Then
            Return False
        End If
        Dim crc_got = buf(data_len + MinFrameLen - 2)
        ReDim data_buf(data_len - 1)
        Array.Copy(buf, 4, data_buf, 0, data_len)
        Dim crc_sum = Calculate.Sum(data_buf)
        If crc_got <> crc_sum Then
            Return False
        End If
        If buf(1) = MsgHeartBeat Then
            If data_len <> Marshal.SizeOf(Slave.WevDriver) Then Return False
            Slave.WevDriver = ByteToStruct(data_buf, GetType(WevHeartBeatSlave_T))
        ElseIf buf(1) = MsgCurve Then
            If data_len <> Marshal.SizeOf(Slave.WevReply) Then Return False
            Slave.WevReply = ByteToStruct(data_buf, GetType(WEVReply_T))
        Else
            Return False
        End If
        Return ret
    End Function

End Class

